﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Script.Serialization;
using TallComponents.Web.Storage;

namespace FolderStructureDemo.Treeview
{
   /// <summary>
   /// Helper methods for AJAX tree view.
   /// </summary>
   public static class TreeViewHelpers
   {
      /// <summary>
      /// Renders markup for a tree view.
      /// </summary>
      /// <param name="clientId">The id for the tree view in the page.</param>
      /// <param name="urlTemplate">The URL template for documents, {0} is substituted with the storagekey for the document.</param>
      /// <param name="storageKey">The storage key of the currently selected document.</param>
      /// <returns>HTML markup for a tree view.</returns>
      public static string TreeView(string clientId, string urlTemplate, string storageKey)
      {
         if (string.IsNullOrEmpty(clientId))
            throw new ArgumentNullException("clientId", "ClientID must not be null or empty.");

         var provider = PdfStorage.Provider as FileSystemStorageProvider;
         if (null == provider)
            throw new InvalidOperationException("TreeView needs the default storage provider to be a FileSystemStorageProvider. Please check your web.config file.");

         string selectedPath;
         if (provider.TryResolveFileName(storageKey, out selectedPath))
         {
            selectedPath = selectedPath.Substring(AbsolutePath(provider.BasePath).Length + 1).Replace("\\", "/");
         }

         var sb = new StringBuilder();
         sb.AppendFormat("<ul id=\"{0}\" class=\"filetree\">", clientId);
         RenderTreeLevel(sb, provider.BasePath, null, selectedPath, urlTemplate);
         sb.AppendLine("</ul>");

         return sb.ToString();
      }

      /// <summary>
      /// Renders the tree level.
      /// </summary>
      /// <param name="sb">The string builder that holds all markup for the tree view.</param>
      /// <param name="basePath">The base path.</param>
      /// <param name="relativePath">The relative path to render.</param>
      /// <param name="selectedPath">The selected path, or <c>null</c> if no document is selected.</param>
      /// <param name="urlTemplate">The URL template for documents, {0} is substituted with the storagekey for the document.</param>
      private static void RenderTreeLevel(StringBuilder sb, string basePath, string relativePath, string selectedPath, string urlTemplate)
      {
         foreach (var item in EnumerateItems(basePath, relativePath))
         {
            var file = item as PdfFileTreeItem;

            if (null != file)
            {
               sb.AppendFormat("<li><span class=\"file\"><a href=\"{0}\">{1}</a></span></li>",
                                string.Format(urlTemplate, file.Document.StorageKey), file.Name);
            }
            else
            {
               var directory = item as DirectoryTreeItem;
               if (null != directory)
               {
                  if (null != selectedPath && selectedPath.StartsWith(directory.Path))
                  {
                     sb.AppendFormat("<li class=\"collapsable\"><span class=\"folder\">{0}</span><ul style=\"display:block;\">",
                                       directory.Name);
                     RenderTreeLevel(sb, basePath, directory.Path, selectedPath, urlTemplate);
                     sb.Append("</ul></li>");
                  }
                  else
                  {
                     sb.AppendFormat(
                                       "<li class=\"hasChildren expandable\" path=\"{1}\"><span class=\"folder\">{0}</span><ul style=\"display:none;\"></ul></li>",
                                       directory.Name, HttpUtility.HtmlAttributeEncode(directory.Path));
                  }
               }
            }

         }
      }

      /// <summary>
      /// Gets the JSON formatted tree view data.
      /// </summary>
      /// <param name="relativePath">The relative path.</param>
      /// <returns>JSON formatted tree view data for the specified relative path.</returns>
      public static string GetJsonTreeViewData(string relativePath)
      {
         var provider = PdfStorage.Provider as FileSystemStorageProvider;
         if (null == provider)
            throw new InvalidOperationException("TreeView needs the default storage provider to be a FileSystemStorageProvider. Please check your web.config file.");

         var serializer = new JavaScriptSerializer();
         return serializer.Serialize(EnumerateItemsForJSONSerialization(provider.BasePath, relativePath, "Default.aspx?doc={0}"));
      }

      /// <summary>
      /// Enumerates the items in a directory and returns objects suitable for JSON serialization.
      /// </summary>
      /// <param name="basePath">The base path.</param>
      /// <param name="relativePath">The relative path.</param>
      /// <param name="urlTemplate">The URL template.</param>
      /// <returns>A set of data objects that instruct the client script how to construct the tree view.</returns>
      private static IEnumerable<object> EnumerateItemsForJSONSerialization(string basePath, string relativePath, string urlTemplate)
      {
         foreach (var item in EnumerateItems(basePath, relativePath))
         {
            var file = item as PdfFileTreeItem;

            if (null != file)
            {
               yield return new
               {
                  text = string.Format("<a href=\"{0}\">{1}</a>", string.Format(urlTemplate, file.Document.StorageKey), item.Name),
                  type = "file"
               };

            }
            else
            {
               var directory = item as DirectoryTreeItem;
               if (null != directory)
               {
                  yield return new { text = item.Name, type = "folder", path = directory.Path, hasChildren = true, };
               }
            }

         }
      }

      /// <summary>
      /// Enumerates the items in a folder
      /// </summary>
      /// <param name="basePath">The base path to the document storage area.</param>
      /// <param name="relativePath">The relative path within the document storage area.</param>
      /// <returns>A set of TreeView items that represent the contents of the folder.</returns>
      private static IEnumerable<TreeItem> EnumerateItems(string basePath, string relativePath)
      {
         string absoluteBasePath = AbsolutePath(basePath);

         if (string.IsNullOrEmpty(relativePath))
         {
            relativePath = string.Empty;
         }
         else if (relativePath.StartsWith("/") || relativePath.StartsWith("\\"))
         {
            relativePath = relativePath.Substring(1);
         }

         string absolutePath = Path.Combine(absoluteBasePath, relativePath);

         foreach (var item in Directory.GetFileSystemEntries(absolutePath).OrderBy(entry => entry))
         {
            if (Directory.Exists(item))
            {
               // It's a directory

               // Get the relative path within the folder hierarchy
               var folderPath = item.Substring(absoluteBasePath.Length).Replace('\\', '/');

               // strip off leading slash
               if (folderPath.StartsWith("/"))
                  folderPath = folderPath.Substring(1);

               // create item data for client script and return it
               yield return new DirectoryTreeItem()
                {
                   AbsolutePath = item,
                   Path = folderPath
                };
            }
            else if (File.Exists(item) && item.EndsWith(".pdf"))
            {
               // It's a PDF document

               var document = PdfStorage.Provider.GetDocument(item);

               if (null != document)
               {
                  // create item data for client script and return it
                  yield return new PdfFileTreeItem()
                               {
                                  AbsolutePath = item,
                                  Document = document,
                               };
               }
            }
         }
      }

      /// <summary>
      /// Returns the file system path represented by the specified <paramref name="virtualPath"/>.
      /// </summary>
      /// <param name="virtualPath">The virtual path.</param>
      /// <returns>An absolute file system path.</returns>
      private static string AbsolutePath(string virtualPath)
      {
         if (null == virtualPath)
            throw new ArgumentNullException("virtualPath");

         string result = virtualPath.Trim();

         if (result.Length == 0)
            throw new ArgumentException("The virtual path must not be empty.", "virtualPath");

         if (virtualPath[0] == '~' || virtualPath[0] == '/')
         {
            result = HttpContext.Current.Server.MapPath(result);
         }
         else if (!Path.IsPathRooted(result))
         {
            result = Path.GetFullPath(result);
         }

         return result;
      }

      /// <summary>
      /// Gets the startup javascript block for a tree view.
      /// </summary>
      /// <param name="clientId">The id of the tree view in the page.</param>
      /// <param name="treeDataCallbackUrl">The URL of the <see cref="AsyncTreeviewInfoHandler"/>.</param>
      /// <returns>
      /// A javascript block that enables the treeview
      /// </returns>
      public static string GetStartupScript(string clientId, string treeDataCallbackUrl)
      {
         if (string.IsNullOrEmpty(clientId))
            throw new ArgumentNullException("clientId", "clientId must not be null or empty.");

         if (string.IsNullOrEmpty(clientId))
            throw new ArgumentNullException("treeDataCallbackUrl", "treeDataCallbackUrl must not be null or empty.");

         return @"
<script type=""text/javascript"">
//<![CDATA[ 
jQuery(document).ready(function() {
   	      $('ul#" + clientId + @"').treeview({ url:'" + treeDataCallbackUrl + @"' })
   	   });
//]]> 
</script>";
      }
   }
}
